using System;

namespace MLSharp.Clustering
{
	/// <summary>
	/// Base class for dataset generators that build datasets for knowledge frontier
	/// discovery testing.
	/// </summary>
	public abstract class KnowledgeFrontierDataSetGeneratorBase : IKnowledgeFrontierDataSetGenerator
	{
		#region Logger

		/// <summary>
		/// The logger for the type.
		/// </summary>
		private static readonly log4net.ILog mLogger =
			log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		/// <summary>
		/// Used to generate random data.
		/// </summary>
		protected Random mRand = new Random();

		/// <summary>
		/// Initializes static properties to their default values.
		/// </summary>
		protected KnowledgeFrontierDataSetGeneratorBase()
		{
			NoiseFactor = 5;
			AttributeFactor = 5;
			Depth = 4;
			BranchingFactor = 2;
			NumberOfInstances = 128;
			FrontierProbability = 0.1;
			AccuracyNoise = 0.05;
		}

		/// <summary>
		/// Sets the random seed used to generate data.
		/// </summary>
		public int Seed
		{
			set
			{
				mRand = new Random(value);
			}
		}

		/// <summary>
		/// The amount of noise to apply to randomly generated instances.
		/// </summary>
		/// <value>The default is 5.</value>
		public double NoiseFactor { get; set; }

		/// <summary>
		/// Determines how many attributes to use to construct each level of the cluster tree.
		/// </summary>
		/// <value>The default is 5.</value>
		public int AttributeFactor { get; set; }

		/// <summary>
		/// The depth of the tree to create.
		/// </summary>
		/// <value>The default is 4.</value>
		public int Depth { get; set; }

		/// <summary>
		/// The number of instances to create.
		/// </summary>
		/// <value>The default is 128.</value>
		public int NumberOfInstances { get; set; }

		/// <summary>
		/// The number of child branches to create at each step down the tree.
		/// </summary>
		/// <value>The default is 2.</value>
		public int BranchingFactor { get; set; }

		/// <summary>
		/// The probability of creating a knowledge frontier.  Larger values
		/// will increase the likelihood of creating frontiers close to the root,
		/// smaller values will bias creation towards the leaves.
		/// </summary>
		/// <value>The default is 0.1.</value>
		public double FrontierProbability { get; set; }

		/// <summary>
		/// The maximum amount of noise to apply to accuracy attributes when creating knowledge frontiers.
		/// </summary>
		/// <value>The default is 0.05.</value>
		public double AccuracyNoise { get; set; }

		/// <summary>
		/// True to create a "FrontierId" attribute that will be populated with the
		/// ID of the frontier node that the corresponding instance belongs to.
		/// </summary>
		/// <value>The default is false.</value>
		public bool LabelFrontiers { get; set; }

		/// <summary>
		/// Generates a synthetic dataset suitable for hierarchical clustering.
		/// </summary>
		/// <returns>The dataset.</returns>
		public abstract IDataSet GenerateDataSet();

		/// <summary>
		/// Builds a skeleton dataset based on the currently configured parameters.  The 
		/// dataset will have empty instances, and the number of attributes will
		/// be computed based on the Depth and <see cref="AttributeFactor"/> settings.
		/// </summary>
		/// <returns></returns>
		protected IDataSet BuildDataSet()
		{
			IDataSet dataSet = new BasicDataSet();
			
			//Calculate how many attributes we need for a given depth and branching factor.  This
			//is a geometric series...
			int numAttributes = AttributeFactor * (Depth * (Depth + 1) / 2);

			if (mLogger.IsDebugEnabled)
			{
				mLogger.DebugFormat("Dataset requires {0} attributes.", numAttributes);
			}

			for (int i=0; i < numAttributes; i++)
			{
				dataSet.Attributes.Add(DataAttribute.NewContinuousAttribute("Att_" + (i + 1)));
			}
			return dataSet;
		}

		/// <summary>
		/// Populates the specified attribute range on the specified instances
		/// with random data that will resemble a cluster across those attributes. 
		/// </summary>
		/// <param name="instanceStartIndex"></param>
		/// <param name="instanceEndIndex"></param>
		/// <param name="attributeStartIndex"></param>
		/// <param name="numAttributes"></param>
		/// <param name="dataSet"></param>
		protected void PopulateInstances(IDataSet dataSet, int instanceStartIndex, int instanceEndIndex, int attributeStartIndex, int numAttributes)
		{
			for (int i = attributeStartIndex; i < (attributeStartIndex + numAttributes); i++)
			{
				//This is the seed value for the attribute.
				double seedValue = mRand.Next();

				//Populate the instances.
				for (int j = instanceStartIndex; j <= instanceEndIndex; j++)
				{
					double noise = (mRand.NextDouble() > 0.5 ? 1.0 : -1.0) * mRand.NextDouble() * NoiseFactor;

					dataSet.Instances[j].Values[i] = seedValue + noise;
				}
			}
		}
	}
}